/*
 * ============================================================================
 *                   GNU Lesser General Public License
 * ============================================================================
 *
 *
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 *
 */
package rad.framework.richfaces;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.faces.event.ActionEvent;
import javax.persistence.EntityManager;

import rad.framework.jpa.EntityPersistenceContextMap;
import rad.framework.jsf.JsfUtil;
import rad.framework.jsf.RadFrameworkFunctions;
import rad.framework.lang.ReflectionUtil;
import rad.framework.richfaces.BaseTreeNode;
import rad.framework.richfaces.Picker;
import rad.framework.seam.AbstractEditor;
import rad.framework.seam.AbstractFinder;
import rad.framework.seam.ColumnType;
import rad.framework.security.jpa.Action;
import rad.framework.security.jpa.PermissionsUtil;

import org.apache.commons.lang.StringUtils;
import org.jboss.seam.Component;
import org.jboss.seam.core.ResourceBundle;
import org.richfaces.model.TreeNode;

/**
 * @author boudyacho
 */
public abstract class FolderTreeNode<E> extends BaseTreeNode {
	private static final long serialVersionUID = 1L;
	
	public FolderTreeNode(TreeNode<?> parent, String id, CardinalityType cardinalityType) {
		super(parent);
		init(parent, id, cardinalityType);
	}
	
	/**
	 * By default the sort values are factors of 10. Thus, it is possible to
	 * override the order of a single folder by using an offset, such as 14, 27, etc.
	 */
	public FolderTreeNode(TreeNode<?> parent, String id, CardinalityType cardinalityType, long sort) {
		super(parent);
		init(parent, id, cardinalityType, sort);
	}

	public FolderTreeNode(TreeNode parent) {
		super(parent);
	}

	protected void init(TreeNode<?> parent, String id, CardinalityType cardinalityType) {
		init(parent, id, cardinalityType, new Long(((BaseTreeNode)parent).getChildCount() + 10));
	}
	
	protected void init(TreeNode<?> parent, String id, CardinalityType cardinalityType, long sort) {
		this.cardinalityType = cardinalityType;
		this.id = id;
		this.key = sort;

		java.util.ResourceBundle bundle = ResourceBundle.instance();
		try {
			this.text = bundle.getString(id);
		} catch (Exception e) {
			log.debug(e.getMessage());
			this.text = id;
		}
		
		try {
			this.typeText = bundle.getString(getEntityType());
		} catch (Exception e) {
			log.debug(e.getMessage());
			this.typeText = getEntityClass().getSimpleName();
		}
		
		try {
			this.icon = bundle.getString(getType() + "TreeNodeIcon");
		} catch (Exception e) {
		}
		
		parent.addChild(getKey(), this);
	}

	//---------------------------------------

	@Override
	public String getType() {
		return "Folder";
	}

	private Object key;

	@Override
	public Object getKey() {
		return key;
	}

	private String id;

	public String getId() {
		return id;
	}

	private String text;

	@Override
	public String getText() {
		return text;
	}

	private String typeText;

	public String getEntityTypeText() {
		return typeText;
	}

	private String icon = "/images/iconFolder.gif";

	@Override
	public String getIcon() {
		return icon;
	}

	//---------------------------------------
	
	public enum CardinalityType {
		M2M, O2M, O2MS, O2O
	}

	private CardinalityType cardinalityType = CardinalityType.O2M;

	public boolean isManyToMany() {
		return CardinalityType.M2M.equals(cardinalityType);
	}

	public boolean isOneToMany() {
		return CardinalityType.O2M.equals(cardinalityType);
	}

	public boolean isOneToManyShared() {
		return CardinalityType.O2MS.equals(cardinalityType);
	}

	public boolean isOneToOne() {
		return CardinalityType.O2O.equals(cardinalityType);
	}

	//---------------------------------------

	@SuppressWarnings("unchecked")
	public void addChild(Object id, TreeNode child) {
		// TODO noop based on permissions filter
		
		if (child instanceof EntityTreeNode) {
			// stop recursive relationships
			EntityTreeNode node = (EntityTreeNode) child;
			if (getRoot().add(node)) {
				super.addChild(id, node);
			}
		} else {
			super.addChild(id, child);
		}
	}

	@SuppressWarnings("unchecked")
	public void clearChildren() {
		for (TreeNode child : children.values()) {
			EntityTreeNode node = (EntityTreeNode) child;
			getRoot().remove(node);
			node.clearChildren();
		}
	}

	protected Collection<E> getCollection() {
		return Collections.EMPTY_LIST;
	}
	
	public E getOneToOne() {
		return null;
	}

	protected void initializeChildren() {
		Collection<E> collection = null;
		if (isOneToOne()) {
			collection = new ArrayList<E>();
			E o = getOneToOne();
			if (o != null) {
				collection.add(o);
			}
		} else {
			collection = getCollection();
		}
		for (E entity : collection) {
			addChild(entity);
		}
	}
	
	//---------------------------------------

	public List<BaseTreeNode> getParentTreeNodes(ArrayList<BaseTreeNode> tree) {
		return ((BaseTreeNode) getParent()).getParentTreeNodes(tree);
	}

	//---------------------------------------

	private Class<E> entityClass;

	@SuppressWarnings("unchecked")
	protected Class<E> getEntityClass() {
		if (entityClass == null) {
			entityClass = ReflectionUtil.getParameterizedType(getClass());
		}
		return entityClass;
	}

	public String getEntityType() {
		return getEntityClass().getSimpleName();
	}

	private AbstractEditor<E> editor = null;
	
	@SuppressWarnings("unchecked")
	public AbstractEditor<E> getEditor() {
		if (editor == null) {
			editor = (AbstractEditor<E>) Component.getInstance(getEditorName());
		}
		return editor; 
	}

	private AbstractFinder<E> finder = null;
	
	@SuppressWarnings("unchecked")
	public AbstractFinder<?> getFinder() {
		if (finder == null) {
			finder = (AbstractFinder<E>) Component.getInstance(getFinderName());
		}
		return finder; 
	}

	protected String getEditorName() {
		return StringUtils.uncapitalize(getEntityType()) + "Editor";
	}

	protected String getFinderName() {
		return StringUtils.uncapitalize(getEntityType()) + "Finder";
	}

	protected String getInstanceName() {
		return StringUtils.uncapitalize(getEntityType());
	}
	
	protected EntityManager getEntityManager() {
		return (EntityManager) Component.getInstance(getPersistenceContextName());
	}
	
	protected String getPersistenceContextName() {
		return EntityPersistenceContextMap.instance()
				.getPersistenceContextNameFor(getEntityClass());
	}

	//---------------------------------------

	protected E selected;
	
	/**
	 * called by view-folder.xhtml, quick-add.xhtml
	 */
	public String save() {
		boolean success = getEditor().save(selected);
		if (success) {
			if (!contains(selected)) {
				// complete bi-directional pointers
				addChild(selected);
			}
			selected = null;
		}
		return null;
	}
	
	protected boolean contains(Object instance) {
		Collection<BaseTreeNode> values = children.values();
		for (BaseTreeNode baseTreeNode : values) {
			if (instance.equals(((EntityTreeNode<?>)baseTreeNode).getInstance())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * called by view-folder.xhtml, quick-add.xhtml
	 */
	public E getSelected() {
		if (selected == null) {
			if (isOneToOne()) {
				if (getChildCount() > 0) {
					EntityTreeNode<E> node = (EntityTreeNode<E>) getChildrenList()
							.get(0);
					selected = node.getInstance();
					return selected;
				}
			}
			selected = getEditor().newInstance();
			initNewInstance(selected);
		}
		return selected;
	}

	/**
	 * called by edit-folder.xhtml, view-folder.xhtml
	 * to prepare an new instance to be entered and saved
	 */
	public String add() {
		E toAdd = getEditor().newInstance();
		initNewInstance(toAdd);
		EntityTreeNode<E> node = addChild(toAdd);
		return node.switchTemp();
	}
	
	/**
	 * just connect parent association
	 * everything else is initialized during editor.createInstance()
	 */
	protected void initNewInstance(E newInstance) {
	}

	/**
	 * entity node factory method
	 */
	public EntityTreeNode addChild(E newInstance) {
		return null;
	}

	public void removeChild2(E newInstance) {
	}
	
	//---------------------------------------
	// toggle or tab control
	//---------------------------------------
	
	private Boolean isRendered = null;
	
	public boolean isRendered() {
		if (isRendered == null) {
			if (getPicker() != null && getPicker() instanceof Picker) {
				isRendered = ((Picker) getPicker()).isRendered();
			} else {
				isRendered = (Boolean) RadFrameworkFunctions.rendered(getOwner().getInstance(),
						getPropertyName());
			}
		}
		return isRendered;
	}
	
	protected String getPropertyName() {
		String name = getId();
		name = name.substring(name.lastIndexOf("_") + 1);
		return name;
	}
	
	public String getAjaxMode() {
		if (isViewMode()) {
			return "client";
		} else {
			return "ajax";
		}
	}
	
	protected boolean opened = false;
	
	public boolean isOpened() {
		return opened;
	}
	
	public void expandListener(ActionEvent event) {
		// TODO only works onclick in ajax mode
		opened = true;
	}
	
	public void collapseListener(ActionEvent event) {
		// TODO is flaky in ajax mode, need to click twice after page reentry/refresh...
		opened = false;
	}

	//---------------------------------------
	// table
	//---------------------------------------
	
	public boolean isRenderTable() {
		return getChildCount() > 0 && !isOneToOne();
	}
	
	public boolean isRenderAction() {
		return true;
	}
	
	public boolean isRenderSiblings() {
		return true;
	}
	
	public Integer getMaxRows() {
		return new Integer(5);
	}
	
	public Integer getMaxPages() {
		return new Integer(20);
	}

	//---------------------------------------
	// add, edit, quick add
	//---------------------------------------
	
	/**
	 * called by edit-folder.xhtml, view-folder.xhtml
	 * to determine whether to render a link to open an edit screen 
	 * isRenderAdd() and isRenderQuickAdd() should be mutually exclusive
	 */
	public boolean isRenderAdd() {
		if (isManyToMany() || isOneToOne()) {
			return false;
		}
		if (!hasPermission(Action.INSERT)) {
			return false;
		}
		if (!isOwnerEditable()) {
			return false;
		}
		if (isViewMode()) {
			return true;
		} else {
			return isMultipart() || isSameTypeAsParent();
		}
	}
	
	/**
	 * called by edit-folder.xhtml, view-folder.xhtml
	 * to determine whether to render the quick add panel
	 * isRenderAdd() and isRenderQuickAdd() should be mutually exclusive
	 */
	public boolean isRenderQuickAdd() {
		if (isMultipart() || isSameTypeAsParent()) {
			// because of technical limitations
			return false;
		}
		if (isViewMode()) {
			return false;
		}
		if (!hasPermission(Action.INSERT)) {
			return false;
		}
		if (!isOwnerEditable()) {
			return false;
		}
		return (isOneToMany() || isOneToManyShared());
	}
	
	/**
	 * called by view-folder.xhtml
	 */
	public boolean isRenderOneToOneView() {
		return isOneToOne() && isViewMode() && getChildCount() > 0
				&& hasPermission(Action.READ);
	}

	/**
	 * called by edit-folder.xhtml
	 */
	public boolean isRenderOneToOneEdit() {
		return isOneToOne() && isEditMode()
				&& hasPermission(Action.INSERT, Action.UPDATE);
	}
	
	//---------------------------------------
	// picker
	//---------------------------------------
	
	private Object picker;

	/**
	 * called by edit-folder.xhtml
	 */
	public Object getPicker() {
		if (picker == null) {
			picker = initPicker();
			if (picker instanceof Picker) {
				((Picker)picker).setFolder(this);
			}
		}
		return picker;
	}

	protected Object initPicker() {
		return JsfUtil.getExpression(getPickerName()).getValue();
	}

	protected String getPickerName() {
		return "#{" + getPropertyName() + "Picker}";
	}
	
	public boolean isRenderPicker() {
		if (isViewMode()) {
			return false;
		}
		if (!isOwnerEditable()) {
			return false;
		}
		return (isManyToMany() || isOneToManyShared());
	}

	//---------------------------------------
	// mode
	//---------------------------------------
	
	public boolean isViewMode() {
		return getOwner().isViewMode();
	}
	
	public boolean isEditMode() {
		return getOwner().isEditMode();
	}
	
	public String getViewPanel() {
		return "includes/view-folder.xhtml";
	}
	
	public String getEditPanel() {
		return "includes/edit-folder.xhtml";
	}

	//---------------------------------------
	// owner
	//---------------------------------------

	public EntityTreeNode<?> getOwner() {
		return (EntityTreeNode<?>) getParent();
	}
	
	protected Boolean isOwnerEditable = null;

	protected boolean isOwnerEditable() {
		if (isOwnerEditable == null) {
			isOwnerEditable = getOwner().getEditor().isRenderEdit();
		}
		return isOwnerEditable;
	}

	protected boolean isSameTypeAsParent() {
		return getOwner().getInstance().getClass().equals(getEntityClass());
	}
	
	// ------------------------------------
	// columns
	// ------------------------------------

	private List<ColumnType> columns;	
	
	public List<ColumnType> getColumns() {
		if (columns == null) {
			columns = new ArrayList<ColumnType>();
			initColumns();
		}
		return columns;
	}
	
	protected void initColumns() {
		List<ColumnType> columns = getFinder().getColumns();
		for (ColumnType columnType : columns) {
			addColumn(columnType);
		}
	}

	protected void addColumn(ColumnType columnType) {
		ColumnType column = new ColumnType(columnType.getName(), columnType.getLabel(), columnType.getType(), columnType
				.getRenderedExpressionString());
		column.setRow(getRow());
		columns.add(column);
	}

	protected void addColumn(String name, String type, String label, String rendered) {
		ColumnType column = new ColumnType(name, ResourceBundle.instance().getString(label), type, rendered);
		column.setRow(getRow());
		columns.add(column);
	}

	protected void addColumn(String name, String type, String rendered) {
		addColumn(name, type, getEntityType() + "_" + name, rendered);
	}

	protected String getRow() {
		return "kid.instance";
	}
	
	// ------------------------------------
	// orderable
	// ------------------------------------
	
	public boolean isOrderable() {
		return false;
	}

	public void setChildrenList(List<TreeNode> list) {
		if (isOrderable()) {
			children.clear();
			Long order = new Long(0);
			for (Iterator i = list.iterator(); i.hasNext();) {
				EntityTreeNode node = (EntityTreeNode) i.next();
				node.setSortOrder(order++);
				super.addChild(node.getKey(), node);
			}
		}
	}

	//---------------------------------------

	protected boolean hasPermission(Action... actions) {
		for (Action action : actions) {
			if (PermissionsUtil.hasPermissions(getEntityClass(),
					action)) {
				return true;
			}
		}
		return false;
	}
	
	public boolean isMultipart() {
		return getOwner().isMultipart();
	}
}
